/**
 * Copyright  2025, Hai Yue Xing He  ZHAO LIMIN
 *
 * @author              ZHAO LIMIN
 * @version             1.0.1
 * @since               2025-04
 * Check.ts             数据检查
 */
import { defined } from './Defined'
import { DataCheckValueError } from './DataCheckValueError'

function getUndefinedErrorMessage(name: string) {
  return `${name} is required, actual value was undefined`
}

function getFailedTypeErrorMessage(actual: string, expected: string, name: string) {
  return `Expected ${name} to be typeof ${expected}, actual typeof was ${actual}`
}

interface DefineChk {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  defined(name: string, test?: any): boolean
}
/**
 * 数据检查用于number 类型
 */
interface NumberChk {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  lessThan(name: string, test: any, limit?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  lessThanOrEquals(name: string, test: any, limit?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  greaterThan(name: string, test: any, limit?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  greaterThanOrEquals(name: string, test: any, limit?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  equals(name1: string, name2: string, test1: any, test2: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkRange(name: string, value?: any, min?: number, max?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkTRange(name: string, value?: any, min?: number, max?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkOutRange(name: string, value?: any, min?: number, max?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkTOutRange(name: string, value?: any, min?: number, max?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkInteger(name: string, value?: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkPositiveInteger(name: string, value?: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkIntegerRange(name: string, value?: any, min?: number, max?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkIntegerTRange(name: string, value?: any, min?: number, max?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkIntegerOutRange(name: string, value?: any, min?: number, max?: number): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  checkIntegerTOutRange(name: string, value?: any, min?: number, max?: number): boolean
}

interface TypeOfDefine extends DefineChk {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  func(name: string, test: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  string(name: string, test: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  number(name: string, test: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  object(name: string, test: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  array(name: string, test: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  map(name: string, test: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  bool(name: string, test: any): boolean
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  bigint(bigint: string, test: any): boolean

  tnumber: NumberChk // 数据类型检查
}
/**
 * Contains functions for checking that supplied arguments are of a specified type
 * or meet specified conditions
 * @private
 */
class CheckTools implements DefineChk {
  /**
   * Throws if test is not defined
   *
   * @param {string} name The name of the variable being tested
   * @param {*} test The value that is to be checked
   * @exception {DataCheckValueError} test must be defined
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  defined(name: string, test?: any): boolean {
    if (!defined(test)) {
      throw new DataCheckValueError(getUndefinedErrorMessage(name))
    }
    return true
  }

  public typeOf: TypeOfDefine = {
    /**
     * Throws if test is not defined
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value that is to be checked
     * @exception {DataCheckValueError} test must be defined
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    defined(name: string, test?: any): boolean {
      if (!defined(test)) {
        throw new DataCheckValueError(getUndefinedErrorMessage(name))
      }
      return true
    },
    /**
     * Throws if test is not typeof 'function'
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DataCheckValueError} test must be typeof 'function'
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    func(name: string, test: any): boolean {
      this.defined('test', test)
      if (typeof test !== 'function') {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'function', name))
      }
      return true
    },
    /**
     * Throws if test is not typeof 'string'
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DataCheckValueError} test must be typeof 'string'
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    string(name: string, test: any): boolean {
      this.defined('test', test)
      if (typeof test !== 'string') {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'string', name))
      }
      return true
    },
    /**
     * Throws if test is not typeof 'number'
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DataCheckValueError} test must be typeof 'number'
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    number(name: string, test: any): boolean {
      this.defined('test', test)
      if (typeof test !== 'number') {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'number', name))
      }
      return true
    },
    /**
     * Throws if test is not typeof 'object'
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DeveloperError} test must be typeof 'object'
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    object(name: string, test: any): boolean {
      if (typeof test !== 'object') {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'object', name))
      }
      return true
    },
    /**
     * Throws if test is not typeof 'Array'
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DeveloperError} test must be typeof 'object'
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    array(name: string, test: any): boolean {
      Check.typeOf.defined(name, test)
      if (!Array.isArray(test)) {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'array', name))
      }
      if (!test || test.length <= 0) {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'array', name))
      }
      return true
    },
    /**
     * Throws if test is not typeof 'Map'
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DeveloperError} test must be typeof 'object'
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    map(name: string, test: any): boolean {
      Check.typeOf.defined(name, test)
      if (!(test instanceof Map)) {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'map', name))
      }
      if (!test || Object.keys(test).length <= 0) {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'map', name))
      }
      return true
    },
    /**
     * Throws if test is not typeof 'boolean'
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DeveloperError} test must be typeof 'boolean'
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    bool(name: string, test: any): boolean {
      if (typeof test !== 'boolean') {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'boolean', name))
      }
      return true
    },
    /**
     * Throws if test is not typeof 'bigint'
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DataCheckValueError} test must be typeof 'bigint'
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    bigint(name: string, test: any): boolean {
      if (typeof test !== 'bigint') {
        throw new DataCheckValueError(getFailedTypeErrorMessage(typeof test, 'bigint', name))
      }
      return true
    },
    /**
     * Throws if test is not typeof 'number' and less than limit
     *
     * @param {string} name The name of the variable being tested
     * @param {*} test The value to test
     * @param {number} limit The limit value to compare against
     * @exception {DeveloperError} test must be typeof 'number' and less than limit
     */
    tnumber: {
      /**
       * Throws if test is not typeof 'number' and less than limit
       *
       * @param {string} name The name of the variable being tested
       * @param {*} test The value to test
       * @param {number} limit The limit value to compare against
       * @exception {DeveloperError} test must be typeof 'number' and less than limit
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      lessThan(name: string, test: any, limit?: number): boolean {
        Check.typeOf.number(name, test)
        if (limit === undefined || test >= limit) {
          throw new DataCheckValueError(
            `Expected ${name} to be less than ${limit}, actual value was ${test}`,
          )
        }
        return true
      },
      /**
       * Throws if test is not typeof 'number' and less than or equal to limit
       *
       * @param {string} name The name of the variable being tested
       * @param {*} test The value to test
       * @param {number} limit The limit value to compare against
       * @exception {DeveloperError} test must be typeof 'number' and less than or equal to limit
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      lessThanOrEquals(name: string, test: any, limit?: number): boolean {
        Check.typeOf.number(name, test)
        if (limit === undefined || test > limit) {
          throw new DataCheckValueError(
            `Expected ${name}  to be less than or equal to ${limit}, actual value was ${test}`,
          )
        }
        return true
      },
      /**
       * Throws if test is not typeof 'number' and greater than limit
       *
       * @param {string} name The name of the variable being tested
       * @param {*} test The value to test
       * @param {number} limit The limit value to compare against
       * @exception {DeveloperError} test must be typeof 'number' and greater than limit
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      greaterThan(name: string, test: any, limit?: number): boolean {
        Check.typeOf.number(name, test)
        if (limit === undefined || test <= limit) {
          throw new DataCheckValueError(
            `Expected ${name} to be greater than ${limit}, actual value was ${test}`,
          )
        }
        return true
      },
      /**
       * Throws if test is not typeof 'number' and greater than or equal to limit
       *
       * @param {string} name The name of the variable being tested
       * @param {*} test The value to test
       * @param {number} limit The limit value to compare against
       * @exception {DeveloperError} test must be typeof 'number' and greater than or equal to limit
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      greaterThanOrEquals(name: string, test: any, limit?: number): boolean {
        Check.typeOf.number(name, test)
        if (limit === undefined || test < limit) {
          throw new DataCheckValueError(
            `Expected ${name}  to be greater than or equal to ${limit}, actual value was ${test}`,
          )
        }
        return true
      },
      /**
       * Throws if test1 and test2 is not typeof 'number' and not equal in value
       *
       * @param {string} name1 The name of the first variable being tested
       * @param {string} name2 The name of the second variable being tested against
       * @param {*} test1 The value to test
       * @param {*} test2 The value to test against
       * @exception {DataCheckValueError} test1 and test2 should be type of 'number' and be equal in value
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      equals(name1: string, name2: string, test1: any, test2: any) {
        Check.typeOf.number(name1, test1)
        Check.typeOf.number(name2, test2)
        if (test1 !== test2) {
          throw new DataCheckValueError(
            `${name1} must be equal to ${name2}, the actual values are ${test1} and ${test2}`,
          )
        }
        return true
      },
      /**
       * 在范围内，包含边界值
       * @param value
       * @param min
       * @param max
       * @returns
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkRange(name: string, value?: any, min?: number, max?: number): boolean {
        Check.typeOf.number(name, value)
        const res =
          (min === undefined ? true : value! >= min) && (max === undefined ? true : value! <= max)
        if (!res) {
          throw new DataCheckValueError(
            `Expected ${name} to be in range ${min} to ${max}, actual value was ${value}`,
          )
        }
        return true
      },
      /**
       * 范围内，不包含边界值
       * @param value
       * @param min
       * @param max
       * @returns
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkTRange(name: string, value?: any, min?: number, max?: number): boolean {
        Check.typeOf.number(name, value)
        const res =
          (min === undefined ? true : value! > min) && (max === undefined ? true : value! < max)
        if (!res) {
          throw new DataCheckValueError(
            `Expected ${name} to be in range ${min} to ${max}, actual value was ${value}`,
          )
        }
        return true
      },
      /**
       * 在范围外, 包含边界值
       * @param value
       * @param min
       * @param max
       * @returns       boolean
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkOutRange(name: string, value?: any, min?: number, max?: number): boolean {
        Check.typeOf.number(name, value)
        const res = !(
          (min === undefined ? true : value! > min) && (max === undefined ? true : value! < max)
        )
        if (!res) {
          throw new DataCheckValueError(
            `Expected ${name} to be out of range ${min} to ${max}, actual value was ${value}`,
          )
        }
        return true
      },
      /**
       *在范围外, 不包含边界值
       * @param value
       * @param min
       * @param max
       * @returns
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkTOutRange(name: string, value?: any, min?: number, max?: number): boolean {
        Check.typeOf.number(name, value)
        const res = !(
          (min === undefined ? true : value! >= min) && (max === undefined ? true : value! <= max)
        )
        if (!res) {
          throw new DataCheckValueError(
            `Expected ${name} to be out of range ${min} to ${max}, actual value was ${value}`,
          )
        }
        return true
      },
      /**
       * 整数检查
       * @param value
       * @returns
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkInteger(name: string, value?: any): boolean {
        Check.typeOf.number(name, value)
        return Number.isInteger(value)
      },
      /**
       * 正整数检查
       * @param value
       * @returns
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkPositiveInteger(name: string, value?: any): boolean {
        Check.typeOf.number(name, value)
        return Number.isInteger(value) && value! > 0
      },
      /**
       * 检查是否为整数，并在数值范围内，含边界
       * @param value
       * @param min
       * @param max
       * @returns boolean
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkIntegerRange(name: string, value?: any, min?: number, max?: number): boolean {
        Check.typeOf.number(name, value)
        return Number.isInteger(value) && this.checkRange(value, min, max)
      },

      /**
       * 检查是否为整数，并在数值范围内，含边界
       * @param value
       * @param min
       * @param max
       * @returns boolean
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkIntegerTRange(name: string, value?: any, min?: number, max?: number): boolean {
        Check.typeOf.number(name, value)
        return Number.isInteger(value) && this.checkTRange(value, min, max)
      },
      /**
       * 检查是否为整数, 并不在指定数值范围，含边界
       * @param value
       * @param min
       * @param max
       * @returns boolean
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkIntegerOutRange(name: string, value?: any, min?: number, max?: number): boolean {
        Check.typeOf.number(name, value)
        return Number.isInteger(value) && this.checkOutRange(value, min, max)
      },
      /**
       * 检查是否为整数, 并不在指定数值范围，不含边界
       * @param value
       * @param min
       * @param max
       * @returns
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      checkIntegerTOutRange(name: string, value?: any, min?: number, max?: number): boolean {
        Check.typeOf.number(name, value)
        return Number.isInteger(value) && this.checkTOutRange(value, min, max)
      },
    }, // chk number
  }
}

export const Check: CheckTools = new CheckTools()
